Skip to content

fix(service-settings): localization 的标准域是执行边界 —— valueDomain 两扇门落地 (#5712) - #6581

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-5712-localization-valuedomain
Aug 8, 2026
Merged

fix(service-settings): localization 的标准域是执行边界 —— valueDomain 两扇门落地 (#5712)#6581
os-project-manager merged 1 commit into
mainfrom
claude/issue-5712-localization-valuedomain

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Closes #5712. The services half of the 2026-08-06 ruling (reading 1): the curated options tables on localization.timezone / localization.currency are UI convenience lists; the enforcement boundary is the standard domain (IANA / ISO 4217). The spec half shipped as #6515 (2fdb36e, SpecifierValueDomainSchema); this PR makes service-settings enforce what that vocabulary declares.

Merged-ref re-verify (first action, per the #6465 discipline)

Re-read at post-merge origin/main (1a53a02) before any code:

What lands

Manifest (localization.manifest.ts): timezone declares iana_time_zone, currency declares iso_4217_currency, and default_country declares iso_3166_alpha2. Options stay curated; descriptions are untouched — they promised these open domains all along, and the declaration is what makes the promise true.

Both doors (settings-service.ts), each at its existing single decision point:

Error code — invalid_value, flagged as a deliberate choice. No FieldErrorCode member names a standard-domain breach. invalid_option was considered and rejected: its catalog line reads "not a member of the field's declared options", and with a domain declared, the declared options are exactly the list a legal value may be outside of — the code would misname the set consulted. invalid_value is the catalog's declared slot for "rejected for a reason no other member names", the same verdict #6199 reached for the step grid. The domain travels in constraint, so clients can branch without parsing prose. HTTP surface unchanged: 400 SETTINGS_VALIDATION with details.fields.

Validator definitions (value-domains.ts, new — follows the spec's pinned TSDoc):

  • iana_time_zone = the Intl.DateTimeFormat probe (the isValidTimeZone shape from resolve-authz-context.ts, restated because it is module-private there). ⛔ NOT Intl.supportedValuesOf('timeZone'): measured on Node 22 it is a 418-entry CLDR subset that omits UTC (the manifest's own default), Asia/Kolkata (a curated option), Europe/Kyiv, US/Eastern, GMT — and renames rather than merely omits (Asia/Calcutta, Europe/Kiev), so using it would replay this card's defect at 418 entries.
  • iso_4217_currency = Intl.supportedValuesOf('currency') (measured usable: 162 entries, CHF in, all 9 curated in, XYZ out), cached once.
  • iso_3166_alpha2 = an explicit list of the 249 officially assigned codes, carried here per the spec handoff (Intl.DisplayNames names ZZ "Unknown Region" and admits the UK alias, so it is not an oracle). Structural pins: size 249, all uppercase pairs; ZZ / UK / XX refused.
  • A misspelt domain on a hand-built manifest (no Zod pass) records nothing — the service-settings: select 型 specifier 的 options 在保存期完全不校验 —— 声明的枚举不被强制 #5131 exhaustive check stays in force rather than opening an accept-everything hole; pinned by test.

default_country: adopted in this PR. The merged enum covers ISO 3166-1 alpha-2, so the third case of the same hole is closed here: ZZ (shape-valid, assigned to nobody) is refused by membership while pattern still owns the shape verdict (ZZZinvalid_format). One deliberate tightening to note: membership is exact uppercase, as the standard spells its codes — us was previously accepted by ^[A-Za-z]{2}$ and is now refused. No in-repo consumer writes lowercase (measured: the key has no runtime reader in-repo).

Regression pin (#5131 semantics survive): a specifier WITHOUT valueDomain is byte-for-byte unchanged — pinned on both doors against mail.provider (sendgrid still refused invalid_option) and localization's own domain-less keys (first_day_of_week, date_format env override).

Reverse verification (direction predicted first)

Predicted: reverting settings-service.ts to origin/main (manifest keeps its declarations, which the old service ignores) turns the new cases red in two distinct ways — acceptance cases red via invalid_option refusals, the ZZ rejection case red by resolving — while the #5131 regression pins and the validator unit pins stay honestly green. Measured: 15 of the 18 new service/route cases went red exactly as predicted, and the 4 survivors are precisely the pins that must not depend on the new limb ("still accepts every curated option", the two #5131 exhaustive pins, the typo-fallback pin). The fixed file was restored and verified byte-identical (diff), then the full suite re-run green.

Verification (all foreground, real readings)

  • pnpm --filter @objectstack/service-settings test16 files, 318 passed, 0 failed (49 new cases across 4 files)
  • pnpm exec tsc --noEmit (in the package) → 13 errors, all pre-existing — exactly the ledgered debt count in check-type-check-coverage.mjs; my files contribute 0. (Two ES2022-lib traps were fixed rather than ledgered: Object.hasOwn and typed supportedValuesOf are spelled ES2020-compatibly with removal notes, since the root lib bump is not this PR's decision.)
  • pnpm exec eslint on all 7 touched files → clean
  • node scripts/check-nul-bytes.mjs → OK; control-byte self-scan of new files → clean
  • Changeset: .changeset/localization-value-domain-enforced.md (patch, @objectstack/service-settings)

Out of scope, filed separately

🤖 Generated with Claude Code

https://claude.ai/code/session_01USNUyHEr7uaU6MoEWXitei


Generated by Claude Code

…rcement boundary — valueDomain enforced on both doors (#5712)

The 2026-08-06 ruling (reading 1): the curated options tables on
localization.timezone / localization.currency are UI convenience lists; the
enforcement boundary is the standard domain. The spec half shipped as #6515
(SpecifierValueDomainSchema); this is the services half.

- manifest: timezone declares iana_time_zone, currency iso_4217_currency,
  default_country iso_3166_alpha2 (third case of the same hole — ZZ passed
  ^[A-Za-z]{2}$)
- both doors judge a declared domain at their one decision point:
  validatePatch (after pattern — shape first, membership second) and
  effectiveEnvOverride (loud error + fallback, #5204 contract unchanged)
- membership follows the spec's pinned definitions: DateTimeFormat probe /
  supportedValuesOf('currency') / explicit 249-code alpha-2 list
- a specifier without valueDomain is byte-for-byte unchanged (#5131
  exhaustive options), pinned by regression tests on both doors
- breach code is invalid_value with constraint { valueDomain } (ADR-0114
  slot for a breach no member names — the #6199 precedent); invalid_option
  would misname the set consulted

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USNUyHEr7uaU6MoEWXitei
@vercel

vercel Bot commented Aug 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 8, 2026 5:49am

Request Review

@github-actions github-actions Bot added the size/l label Aug 8, 2026
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-settings.

7 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/kernel/runtime-services/audit-service.mdx (via packages/services/service-settings)
  • content/docs/kernel/runtime-services/index.mdx (via packages/services/service-settings)
  • content/docs/kernel/runtime-services/settings-service.mdx (via packages/services/service-settings)
  • content/docs/plugins/packages.mdx (via @objectstack/service-settings)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/service-settings)
  • content/docs/releases/implementation-status.mdx (via @objectstack/service-settings)
  • content/docs/releases/v9.mdx (via @objectstack/service-settings)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

localization 的 timezone / currency 用「策展式 options 表」当穷尽执行边界:合法的 IANA 时区与 ISO 4217 币种被拒

2 participants